package com.daifeng.gulimall.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.daifeng.common.utils.R;
import com.daifeng.common.vo.MemberResponseVO;
import com.daifeng.gulimall.order.Interceptor.OrderInterceptor;
import com.daifeng.gulimall.order.constant.OrderConstant;
import com.daifeng.gulimall.order.entity.OrderEntity;
import com.daifeng.gulimall.order.entity.OrderItemEntity;
import com.daifeng.gulimall.order.enume.OrderStatusEnum;
import com.daifeng.gulimall.order.feign.CartServiceFeign;
import com.daifeng.gulimall.order.feign.menberServiceFeign;
import com.daifeng.gulimall.order.feign.productServiceFeign;
import com.daifeng.gulimall.order.service.OrderTradeService;
import com.daifeng.gulimall.order.to.OrderCreateTo;
import com.daifeng.gulimall.order.vo.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class OrderTradeServiceImpl implements OrderTradeService {

    @Resource
    CartServiceFeign cartServiceFeign;

    @Resource
    menberServiceFeign menberService;

    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Resource
    productServiceFeign  productService;

    /**
     * 结算
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Override
    public OrderConfirmVo toTrade() throws ExecutionException, InterruptedException {
        OrderConfirmVo item = new OrderConfirmVo();
        MemberResponseVO memberResponseVO = OrderInterceptor.threadLocal.get();
        //1. 查询用户积分
        item.setIntegration(memberResponseVO.getIntegration());

        // 在主线程是获取上下文数据
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        //2. 获取用户地址
        CompletableFuture<Void> addressfuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            R r = menberService.getaddressByid(memberResponseVO.getId());
            List<MemberAddressVo> addressVo = JSON.parseObject(JSON.toJSONString(r.get("data")), new TypeReference<List<MemberAddressVo>>() {
            });
            item.setAddressVos(addressVo);
        },executor);

        //3. 获取商品数据
        CompletableFuture<Void> itemressfuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> checkItems = cartServiceFeign.getCheckItems();
            item.setItems(checkItems);
        },executor);

        // 4、防重令牌
        /**
         * 接口幂等性就是用户对同一操作发起的一次请求和多次请求结果是一致的
         * 不会因为多次点击而产生了副作用，比如支付场景，用户购买了商品，支付扣款成功，
         * 但是返回结果的时候出现了网络异常，此时钱已经扣了，用户再次点击按钮，
         * 此时就会进行第二次扣款，返回结果成功，用户查询余额发现多扣钱了，
         * 流水记录也变成了两条。。。这就没有保证接口幂等性
         */
        // 先是再页面中生成一个随机码把他叫做token先存到redis中，然后放到对象中在页面进行渲染。
        // 用户提交表单的时候，带着这个token和redis里面去匹配如果一直那么可以执行下面流程。
        // 匹配成功后再redis中删除这个token，下次请求再过来的时候就匹配不上直接返回
        // 生成防重令牌
        String token = UUID.randomUUID().toString().replace("-","");
        // 存到redis中 设置30分钟超时
        redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVO.getId(),token,30, TimeUnit.SECONDS);
        // 放到页面进行显示token，然后订单中带着token来请求
        item.setOrderToken(token);

        //等待所有任务都完成
        CompletableFuture.allOf(itemressfuture,addressfuture).get();
        return item;
    }


    /**
     * 提交订单
     * @param orderSubmitVo
     * @return
     */
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo orderSubmitVo) {
        SubmitOrderResponseVo responseVo = new SubmitOrderResponseVo();
        // 设置状态码
        responseVo.setCode(0);
        // 通过拦截器拿到用户的数据
        MemberResponseVO  memberRespVo = OrderInterceptor.threadLocal.get();
        // 下单： 创建订单、验证令牌、验证价格、验证库存
        //  1.验证令牌
        //获取令牌
        String token = orderSubmitVo.getOrderToken();
        /**
         * 不使用原子性验证令牌
         *      1、用户带着两个订单，提交速度非常快，两个订单的令牌都是123，去redis里面查查到的也是123。
         *          两个对比都通过，然后来删除令牌，那么就会出现用户重复提交的问题，
         *      2、第一次差的快，第二次查的慢，只要没删就会出现这些问题
         *      3、因此令牌的【验证和删除必须保证原子性】
         *      String orderToken = vo.getOrderToken();
         *      String redisToken = redisTemplate.opsForValue().get(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());
         *         if (orderToken != null && orderToken.equals(redisToken)) {
         *             // 令牌验证通过 进行删除
         *             redisTemplate.delete(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());
         *         } else {
         *             // 不通过
         *         }
         */
        // 验证令牌【令牌的对比和删除必须保证原子性】
        // 因此使用redis中脚本来进行验证并删除令牌
        // 0【删除失败/验证失败】 1【删除成功】
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        /**
         * 	public <T> T execute(RedisScript<T> script // redis的脚本
         * 	    , List<K> keys // 对应的key 参数中使用了Array.asList 将参数转成list集合
         * 	    , Object... args) { // 要删除的值
         */
        // 原子验证和删除
        Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class)
                , Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId())
                , token);
        if(result == 0L){  //令牌验证失败
            responseVo.setCode(1);
        }else {
            // 1、创建订单、订单项信息
            OrderCreateTo order = createOrder();
            // 2、应付总额
            BigDecimal payAmount = order.getOrder().getPayAmount();
            // 应付价格
            BigDecimal payPrice = orderSubmitVo.getPayPrice();
            // 锁库存
        }
        return responseVo;
    }

    /**创建订单
     *
     * @return
     */
    private OrderCreateTo createOrder() {
        OrderCreateTo orderCreateTo = new OrderCreateTo();
        // 1.生成订单号
        String orderSn = IdWorker.getTimeId();
        // 2.构建订单
        OrderEntity order = buildOrder(orderSn);
        // 3.构建订单项
        List<OrderItemEntity> orderItems = builderOrderItems(orderSn);
        // 4、设置价格、积分相关信息
        computPrice(order,orderItems);
        // 5、设置订单项
        orderCreateTo.setOrderItems(orderItems);
        // 6、设置订单
        orderCreateTo.setOrder(order);
        return orderCreateTo;
    }

    /**
     * 计算订单涉及到的积分、优惠卷抵扣、促销优惠信息等信息
     * @param orderEntity
     * @param itemEntities
     * @return
     */
    private OrderEntity computPrice(OrderEntity orderEntity, List<OrderItemEntity> itemEntities) {
        // 1、定义好相关金额，然后遍历购物项进行计算
        // 总价格
        BigDecimal total = new BigDecimal("0");
        //相关优惠信息
        // 优惠卷抵扣金额
        BigDecimal coupon = new BigDecimal("0");
        // 积分优惠金额
        BigDecimal integration = new BigDecimal("0");
        // 促销优惠金额
        BigDecimal promotion = new BigDecimal("0");
        // 积分
        BigDecimal gift = new BigDecimal("0");
        // 成长值
        BigDecimal growth = new BigDecimal("0");

        // 遍历订单项将所有的优惠信息进行相加
        for (OrderItemEntity itemEntity : itemEntities) {
            coupon = coupon.add(itemEntity.getCouponAmount()); // 优惠卷抵扣
            integration = integration.add(itemEntity.getIntegrationAmount()); // 积分优惠分解金额
            promotion = promotion.add(itemEntity.getPromotionAmount()); // 商品促销分解金额
            gift = gift.add(new BigDecimal(itemEntity.getGiftIntegration().toString())); // 赠送积分
            growth = growth.add(new BigDecimal(itemEntity.getGiftGrowth())); // 赠送成长值
            total = total.add(itemEntity.getRealAmount()); //优惠后的总金额
        }

        // 2、设置订单金额
        // 订单总金额
        orderEntity.setTotalAmount(total);
        // 应付总额 = 订单总额 + 运费信息
        orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));
        // 促销优化金额（促销价、满减、阶梯价）
        orderEntity.setPromotionAmount(promotion);
        // 优惠券抵扣金额
        orderEntity.setCouponAmount(coupon);

        // 3、设置积分信息
        // 订单购买后可以获得的成长值
        orderEntity.setGrowth(growth.intValue());
        // 积分抵扣金额
        orderEntity.setIntegrationAmount(integration);
        // 可以获得的积分
        orderEntity.setIntegration(gift.intValue());
        // 删除状态【0->未删除；1->已删除】
        orderEntity.setDeleteStatus(0);
        return orderEntity;

   }

    /**
     * 构建订单项
     * @param orderSn
     * @return
     */
    private List<OrderItemEntity> builderOrderItems(String orderSn) {
      //  List<OrderItemEntity>
        List<OrderItemVo> checkItems = cartServiceFeign.getCheckItems();
        List<OrderItemEntity> collect = checkItems.stream().map(item -> {
            OrderItemEntity orderItem = builderOrderItem(item);
            orderItem.setOrderSn(orderSn);
            return orderItem;
        }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 构建订单项信息
     * @param item
     * @return
     */
    private OrderItemEntity builderOrderItem(OrderItemVo item) {
        OrderItemEntity order = new OrderItemEntity();
        //1.设置sku信息
        order.setSkuId(item.getSkuid());
        order.setSkuName(item.getTitle());
        order.setSkuPic(item.getImage());
        order.setSkuPrice(item.getPrice());
        // 商品属性以 ; 拆分
        String skuAttr = StringUtils.collectionToDelimitedString(item.getSkuAttr(), ";");
        order.setSkuAttrsVals(skuAttr);
        // 商品购买数量
        order.setSkuQuantity(item.getCount());
        //2.设置积分 成长值
        // 赠送积分 移弃小数值
        order.setGiftIntegration(item.getPrice().multiply(new BigDecimal(item.getCount().toString())).intValue());
        // 赠送成长值
        order.setGiftGrowth(item.getPrice().multiply(new BigDecimal(item.getCount().toString())).intValue());
        // 3.设置商品spu信息
        SpuInfoVo infoVo = productService.getInfoBySkuId(item.getSkuid());
        // 品牌信息
        order.setSpuBrand(infoVo.getBrandId().toString());
        // 商品分类信息
        order.setCategoryId(infoVo.getCatalogId());
        // spuid
        order.setSpuId(infoVo.getId());
        // spu_name 商品名字
        order.setSpuName(infoVo.getSpuName());
        // 5、设置商品积分信息
        // 赠送积分 移弃小数值
        order.setGiftIntegration(item.getPrice().multiply(new BigDecimal(item.getCount().toString())).intValue());
        // 赠送成长值
        order.setGiftGrowth(item.getPrice().multiply(new BigDecimal(item.getCount().toString())).intValue());

        // 6、订单项的价格信息
        // 这里需要计算商品的分解信息
        // 商品促销分解金额
        order.setPromotionAmount(new BigDecimal("0"));
        // 优惠券优惠分解金额
        order.setCouponAmount(new BigDecimal("0"));
        // 积分优惠分解金额
        order.setIntegrationAmount(new BigDecimal("0"));
        // 商品价格乘以商品购买数量=总金额(未包含优惠信息)
        BigDecimal origin = order.getSkuPrice().multiply(new BigDecimal(order.getSkuQuantity().toString()));
        // 总价格减去优惠卷-积分优惠-商品促销金额 = 总金额
        origin.subtract(order.getPromotionAmount())
                .subtract(order.getCouponAmount())
                .subtract(order.getIntegrationAmount());
        // 该商品经过优惠后的分解金额
        order.setRealAmount(origin);
        return order;

    }

    /**
     * 构建订单
     * @param orderSn
     * @return
     */
    private OrderEntity buildOrder(String orderSn) {
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(orderSn);
        // 通过拦截器拿到用户的数据
        MemberResponseVO  memberRespVo = OrderInterceptor.threadLocal.get();
        // 设置用户信息
        orderEntity.setMemberUsername(memberRespVo.getUsername());
        // 用户id
        orderEntity.setMemberId(memberRespVo.getId());
        // 设置地址信息 将查询到的会员收货地址信息设置到订单对象中
        R fare = menberService.Fare(memberRespVo.getId());
        FareVo fareVo =JSON.parseObject(JSON.toJSONString(fare.get("data")),new TypeReference<FareVo>(){}) ;
        //运费
        orderEntity.setFreightAmount(fareVo.getFare());
        // 是否确认收货  默认为未收货
        orderEntity.setConfirmStatus(0);
//        orderEntity.setBillReceiverPhone(fareVo.getAddressVo().getPhone());
//        orderEntity.setBillReceiverEmail(memberRespVo.getEmail());
        // 收货人手机号
        orderEntity.setReceiverPhone(fareVo.getMenberAddressVo().getPhone());
        // 收货人邮编
        orderEntity.setReceiverPostCode(fareVo.getMenberAddressVo().getPostCode());
        // 省份/直辖市
        orderEntity.setReceiverProvince(fareVo.getMenberAddressVo().getProvince());
        // 城市
        orderEntity.setReceiverCity(fareVo.getMenberAddressVo().getCity());
        //区
        orderEntity.setReceiverRegion(fareVo.getMenberAddressVo().getRegion());
        // 详细地址
        orderEntity.setReceiverDetailAddress(fareVo.getMenberAddressVo().getDetailAddress());
        // 订单刚创建状态设置为 待付款，用户支付成功后将该该状态改成已付款
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        // 自动确认时间
        orderEntity.setAutoConfirmDay(7);
        return orderEntity;
    }


}
